package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.respository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @Author atguigu-mqx
 * @Description TODO
 * @Date 2023/8/26 11:47
 * @Version 1.0
 */
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;

    //  封装好的接口，这个接口中有CRUD 方法，同时这个接口，能够做到启动项目的时候，就自动创建索引库.
    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient client;

    @Override
    public void incrHotScore(Long skuId) {
        //  存储缓存,必须有缓存的key = skuId
        //  String 获取原来值+1; incr incrby
        //  ZSet zincrby hotScore 1 27 优先选择
        //  定义key
        String key = "hotScore";
        //  自增
        Double count = this.redisTemplate.opsForZSet().incrementScore(key, "skuId:" + skuId, 1);
        //  判断
        if (count%10==0){
            //  更新一次es
            //  先获取数据
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            //  将最新的热度排名值赋值
            goods.setHotScore(count.longValue());
            //  将最新对象保存到es
            this.goodsRepository.save(goods);
        }
    }

    @Override
    public SearchResponseVo search(SearchParam searchParam) {
        /*
        SearchRequest searchRequest = new SearchRequest();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        GET /goods/_search
        {
          "query": {
            "match_all": {}
           }
        }
         */
        //  1.  声明给查询对象
        SearchRequest searchRequest = this.queryBuildDsl(searchParam);
        //  2.  执行查询方法
        SearchResponse searchResponse = null;
        try {
            searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  3.  获取到查询结果集并返回数据
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        //  每页显示的条数
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //  设置默认页
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //  计算总页数 10 3 4 | 9 3 3
        //  long totalPages = searchResponseVo.getTotal()%searchResponseVo.getPageSize()==0?searchResponseVo.getTotal()/searchResponseVo.getPageSize():searchResponseVo.getTotal()/searchResponseVo.getPageSize()+1;
        long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        //  返回数据
        return searchResponseVo;
    }

    /**
     * 返回结果数据
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseResult(SearchResponse searchResponse) {
        /*
        private List<SearchResponseTmVo> trademarkList;
        private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        private List<Goods> goodsList = new ArrayList<>();
        private Long total;//总记录数
        private Integer pageSize;//每页显示的内容
        private Integer pageNo;//当前页面
        private Long totalPages;//总页数
         */
        //  获取hits
        SearchHits hits = searchResponse.getHits();
        //  创建对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        //  赋值总记录数据
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //  获取商品集合的hits
        SearchHit[] subHits = hits.getHits();
        //  创建商品集合
        List<Goods> goodsList = new ArrayList<>();
        //  赋值商品集合
        if (null != subHits && subHits.length > 0){
            //  循环遍历
            for (SearchHit subHit : subHits) {
                //  获取资源数据
                String sourceJson = subHit.getSourceAsString();
                //  将_source 转换为实体对象
                Goods goods = JSON.parseObject(sourceJson, Goods.class);
                //  如果用户是通过全文检索的时候，title 需要高亮显示! subHit.getHighlightFields()==Map==>title
                if (null != subHit.getHighlightFields().get("title")) {
                    //  获取到高亮的title
                    String highlightTitle = subHit.getHighlightFields().get("title").getFragments()[0].toString();
                    //  覆盖title 数据
                    goods.setTitle(highlightTitle);
                }
                //  将对象添加到集合中
                goodsList.add(goods);
            }
        }
        //  给集合赋值
        searchResponseVo.setGoodsList(goodsList);
        //  品牌数据从聚合中获取数据
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //        List<SearchResponseTmVo> trademarkList = new ArrayList<>();
        //        for (Terms.Bucket bucket : tmIdAgg.getBuckets()) {
        //            //  创建对象
        //            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
        //            //  获取品牌Id 数据
        //            String tmIdString = bucket.getKeyAsString();
        //            //  赋值品牌Id
        //            searchResponseTmVo.setTmId(Long.parseLong(tmIdString));
        //            //  将这个对象添加到集合
        //            trademarkList.add(searchResponseTmVo);
        //        }
        //  SearchResponseTmVo tmId tmName tmLogoUrl
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            //  创建对象
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //  获取品牌Id 数据
            String tmIdString = bucket.getKeyAsString();
            //  赋值品牌Id
            searchResponseTmVo.setTmId(Long.parseLong(tmIdString));
            //  还需要品牌的名称
            ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //  还需要品牌的url
            ParsedStringTerms tmLogoUrlAgg = bucket.getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            //  返回数据
            return searchResponseTmVo;
        }).collect(Collectors.toList());

        //  赋值品牌数据
        searchResponseVo.setTrademarkList(trademarkList);
        //  从聚合中获取平台属性数据
        //  List<SearchResponseAttrVo> attrsList = new ArrayList<>();
        //  获取到nested 聚合类型
        ParsedNested attrsAgg = (ParsedNested) aggregationMap.get("attrsAgg");
        //  获取到平台属性Id聚合
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        List<SearchResponseAttrVo> attrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //  创建平台属性渲染对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //  赋值平台属性id
            String attrId = bucket.getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));

            //  获取到平台属性名
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);
            //  获取到平台属性值名
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> valueNameList = attrValueAgg.getBuckets().stream().map(bucket1 -> bucket1.getKeyAsString()).collect(Collectors.toList());
            List<String> attrValueList = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
            //  赋值平台属性值集合
            searchResponseAttrVo.setAttrValueList(attrValueList);
            //  返回数据
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        //  赋值平台属性数据
        searchResponseVo.setAttrsList(attrVoList);
        //  返回数据
        return searchResponseVo;
    }

    /**
     * 返回查询请求对象
     * @param searchParam
     * @return
     */
    private SearchRequest queryBuildDsl(SearchParam searchParam) {
        //  声明一个查询器{}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //  query --> bool
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //  判断 属于通过三级分类Id过滤
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())){
            //  filter --> term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())){
            //  filter --> term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())){
            //  filter --> term
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }

        //  有可能按照全文检索关键词查询.
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            //  bool-->must
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //  设置高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //  设置高亮的字段与格式
            highlightBuilder.field("title");
            highlightBuilder.preTags("<span style=color:red>");
            highlightBuilder.postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        //  是否根据品牌进行过滤 trademark=1:小米
        String trademark = searchParam.getTrademark();
        if (!StringUtils.isEmpty(trademark)){
            //  按照品牌Id 过滤.
            String[] split = trademark.split(":");
            if (null != split && split.length==2){
                // tmId = split[0];
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        //  是否根据平台属性进行过滤
        String[] props = searchParam.getProps();
        if (null != props && props.length>0){
            //  循环遍历
            for (String prop : props) {
                //  prop=12534:16g:内存
                String[] split = prop.split(":");
                // 内层嵌套查询对象
                BoolQueryBuilder innerBoolQueryBuilder = QueryBuilders.boolQuery();
                if (null != split && split.length ==3){
                    //  构建内层嵌套查询条件
                    innerBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    innerBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                }
                //  将内层查询条件赋值给外层bool-->filter-->nested
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",innerBoolQueryBuilder, ScoreMode.None));
            }
        }

        //  分页  from size
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(searchParam.getPageSize());

        //  排序  sort title desc
        //  order=1:desc order=1:asc order=2:desc order=2:asc  1:表示综合 hotScore 2:price 价格
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //  获取排序内容
            String[] split = order.split(":");
            //  声明一个变量，来存储排序类型
            String sortField = "";
            if (null != split && split.length==2){
                //  判断根据什么类型进行排序
                switch (split[0]){
                    case "1":
                        sortField = "hotScore";
                        break;
                    case "2":
                        sortField = "price";
                        break;
                }
                //  设置排序规则
                searchSourceBuilder.sort(sortField, "asc".equals(split[1])?SortOrder.ASC:SortOrder.DESC);
            }
        } else {
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }
        //  聚合: 品牌聚合
        searchSourceBuilder.aggregation(
                AggregationBuilders.terms("tmIdAgg").field("tmId")
                        .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                        .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"))
        );

        //  平台属性聚合：
        searchSourceBuilder.aggregation(
                AggregationBuilders.nested("attrsAgg","attrs")
                        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))
                        )
        );
        searchSourceBuilder.query(boolQueryBuilder);

        //  查询的时候， GET /goods/_search{}
        //  创建查询对象
        SearchRequest searchRequest = new SearchRequest("goods");
        //  设置查询器需要获取字段的数据
        searchSourceBuilder.fetchSource(new String []{"id","defaultImg","title","price","createTime"},null);
        //  查询的时候，只有"id","defaultImg","title","price","createTime" 字段是有数据的，其他字段都是null
        System.out.println("dsl:\t"+searchSourceBuilder.toString());
        //  将 dsl 语句放入查询请求对象 控制器
        searchRequest.source(searchSourceBuilder);
        //  返回查询对象
        return searchRequest;
    }

    @Override
    public void upperGoods(Long skuId) {
        //  获取到数据给哪个对象? Goods
        //  如何将这个对象保存到es? 使用es 的工具类.
        Goods goods = new Goods();
        //  上架：goods 赋值：
        //  runAsync() void suppleAsync() U
        CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
            goods.setId(skuId);
            goods.setTitle(skuInfo.getSkuName());
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            //  可能会出现数据不一致.
            //  goods.setPrice(skuInfo.getPrice().doubleValue());
            //  表示查询最新价格
            goods.setPrice(this.productFeignClient.getSkuPrice(skuId).doubleValue());
            goods.setCreateTime(new Date());
            return skuInfo;
        });


        CompletableFuture<Void> tmCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //  获取品牌数据并赋值
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            goods.setTmId(trademark.getId());
            goods.setTmName(trademark.getTmName());
            goods.setTmLogoUrl(trademark.getLogoUrl());
        });

        CompletableFuture<Void> cateCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
            //  获取分类数据并赋值
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            goods.setCategory1Id(categoryView.getCategory1Id());
            goods.setCategory2Id(categoryView.getCategory2Id());
            goods.setCategory3Id(categoryView.getCategory3Id());
            goods.setCategory1Name(categoryView.getCategory1Name());
            goods.setCategory2Name(categoryView.getCategory2Name());
            goods.setCategory3Name(categoryView.getCategory3Name());

        });


        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            //  获取拼图属性数据并赋值.
            //  根据skuId 找到对应的平台属性与平台属性值
            List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
            //    ArrayList<SearchAttr> searchAttrArrayList = new ArrayList<>();
            //    if (!CollectionUtils.isEmpty(attrList)) {
            //        //  循环遍历
            //        attrList.forEach(baseAttrInfo -> {
            //            SearchAttr searchAttr = new SearchAttr();
            //            searchAttr.setAttrId(baseAttrInfo.getId());
            //            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            //            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            //            searchAttrArrayList.add(searchAttr);
            //        });
            //    }
            //    //  赋值数据
            //    goods.setAttrs(searchAttrArrayList);
            if (!CollectionUtils.isEmpty(attrList)) {
                List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                    SearchAttr searchAttr = new SearchAttr();
                    searchAttr.setAttrId(baseAttrInfo.getId());
                    searchAttr.setAttrName(baseAttrInfo.getAttrName());
                    searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                    return searchAttr;
                }).collect(Collectors.toList());
                //  赋值操作
                goods.setAttrs(searchAttrList);
            }
        });
        //  将多线程数据汇总
        CompletableFuture.allOf(skuInfoCompletableFuture,tmCompletableFuture,cateCompletableFuture,attrCompletableFuture).join();
        //  商品上架保存数据
        this.goodsRepository.save(goods);
    }

    @Override
    public void lowerGoods(Long skuId) {
        this.goodsRepository.deleteById(skuId);
    }
}
